package com.lai.qq.route.gateway.filter;

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.rewrite.CachedBodyOutputMessage;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.cloud.gateway.support.BodyInserterContext;
import org.springframework.cloud.gateway.support.DefaultServerRequest;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.reactive.function.BodyInserter;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.net.URI;
import java.sql.Timestamp;
import java.util.List;
import java.util.Map;

/**
 * @author: lai.quanqiang
 * @date : 2020/6/30
 */
@Component
@Slf4j
public class LogRequestBodyGatewayFilterFactory extends AbstractGatewayFilterFactory<Object> {


    @Override
    public GatewayFilter apply(Object config) {

        return new WriteLogRequestGatewayFilter();
    }

    public class WriteLogRequestGatewayFilter implements GatewayFilter, Ordered {


        @SneakyThrows
        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            ServerRequest serverRequest = new DefaultServerRequest(exchange);
            // 获取用户传来的数据类型
            MediaType mediaType = exchange.getRequest().getHeaders().getContentType();
            // 如果是json格式，将body内容转化为object or map 都可
            if (MediaType.APPLICATION_JSON.isCompatibleWith(mediaType)){
                Mono<Object> modifiedBody = serverRequest.bodyToMono(Object.class)
                        .flatMap(body -> {
                            recordLog(exchange, body);
                            return Mono.just(body);
                        });

                return getVoidMono(exchange, chain, Object.class, modifiedBody);
            }
            // 如果是表单请求
            else if(MediaType.APPLICATION_FORM_URLENCODED.isCompatibleWith(mediaType)){
                Mono<String> modifiedBody = serverRequest.bodyToMono(String.class)
                        // .log("modify_request_mono", Level.INFO)
                        .flatMap(body -> {
                            recordLog(exchange, body);
                            return Mono.just(body);
                        });

                return getVoidMono(exchange, chain, String.class, modifiedBody);
            }
            // TODO 这里未来还可以限制一些格式
            //return chain.filter(exchange.mutate().request(exchange.getRequest()).build());
            return chain.filter(exchange);
        }

        @Override
        public int getOrder() {
            return 0;
        }

        /**
         * 记录到请求日志中去
         * @param exchange exchange
         * @param body 请求的body内容
         */
        private void recordLog(ServerWebExchange exchange, Object body) {
            Route gatewayUrl = exchange.getRequiredAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
            URI uri = gatewayUrl.getUri();
            ServerHttpRequest request = exchange.getRequest();
            String URIPath = request.getURI().toString();
            String path = request.getPath().value();
            String method = request.getMethodValue();
            String instance = uri.getAuthority();
            Timestamp timestamp=new Timestamp(System.currentTimeMillis());//转换成系统时间时分秒
            StringBuilder prams = new StringBuilder();
            // 处理get的请求
            if (null != method && HttpMethod.GET.matches(method)) {
                // 记录请求的参数信息 针对GET 请求
                MultiValueMap<String, String> queryParams = request.getQueryParams();
                for (Map.Entry<String, List<String>> entry : queryParams.entrySet()) {
                    prams.append(entry.getKey()).append("=").append(StringUtils.join(entry.getValue(), ",")).append(",");
                }
            } else {
                // 从body中读取参数
                prams.append(body);
            }
            log.info("输入参数信息:timestamp={},uri={},URIPath={},path={},method={},instance={},prams={}",timestamp,uri,URIPath,
                    path,method,instance,prams);
        }

    }

    /**
     * 参照 ModifyRequestBodyGatewayFilterFactory.java 截取的方法
     * @param exchange
     * @param chain
     * @param outClass
     * @param modifiedBody
     * @return
     */
    private Mono<Void> getVoidMono(ServerWebExchange exchange, GatewayFilterChain chain, Class outClass, Mono<?> modifiedBody) {
        BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, outClass);
        HttpHeaders headers = new HttpHeaders();
        headers.putAll(exchange.getRequest().getHeaders());

        // the new content type will be computed by bodyInserter
        // and then set in the request decorator
        headers.remove(HttpHeaders.CONTENT_LENGTH);


        CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, headers);
        return bodyInserter.insert(outputMessage,  new BodyInserterContext())
                .then(Mono.defer(() -> {
                    ServerHttpRequestDecorator decorator = new ServerHttpRequestDecorator(
                            exchange.getRequest()) {
                        @Override
                        public HttpHeaders getHeaders() {
                            long contentLength = headers.getContentLength();
                            HttpHeaders httpHeaders = new HttpHeaders();
                            httpHeaders.putAll(super.getHeaders());
                            if (contentLength > 0) {
                                httpHeaders.setContentLength(contentLength);
                            } else {
                                // TODO: this causes a 'HTTP/1.1 411 Length Required' on httpbin.org
                                httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
                            }
                            return httpHeaders;
                        }

                        @Override
                        public Flux<DataBuffer> getBody() {
                            return outputMessage.getBody();
                        }
                    };
                    return chain.filter(exchange.mutate().request(decorator).build());
                }));
    }



}
